﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace Othello_Game
{
    public partial class MainScreen : Form
    {
        #region data members

        public static bool UseMiniMax = true;
        
        /// <summary>
        /// The number of times black player won
        /// </summary>
        private int playerOneWins = 0;
        /// <summary>
        /// The number of times white player won
        /// </summary>
        private int playerTwoWins = 0;
        /// <summary>
        /// The number of times there was a tie
        /// </summary>
        private int ties = 0;

        private System.Windows.Forms.Timer computerTimer = new System.Windows.Forms.Timer();

        private Color boardColor = Color.FromArgb(255, Color.Yellow);
        private Color playerOneColor = Color.FromArgb(200, Color.Black);
        private Color playerTwoColor = Color.FromArgb(180, Color.White);

        private static int rows = GeneralConsts.LEGAL_BOARD_SIZE;
        private static int columns = GeneralConsts.LEGAL_BOARD_SIZE;
        private players currentPlayer = players.PLAYER1;
        private Board board = new Board(rows, columns);

        private Button[,] boardButtons = new Button[rows, columns];

        /// <summary>
        /// The settings form
        /// </summary>
        private SettingsPage SP;

        /// <summary>
        /// Describes whether the game is against the PC or real player
        /// </summary>
        private bool playingAgainstComputer = true;

        /// <summary>
        /// Describes the computer's strategy level 
        /// </summary>
        public static int Difficulty { get; set; }

        #endregion

        #region Ctor
        /// <summary>
        /// Initializes an instance of the Othello_Game.MainScreen class
        /// </summary>
        public MainScreen()
        {
            InitializeComponent();
            MoreInitializing();
            SP = new SettingsPage(this.playerOneColor, this.playerTwoColor, this.BackColor, this.boardColor);
            SP.applyButton.Click += new EventHandler(applyColors);
            SP.okButton.Click += new EventHandler(okButton_Click);
            SP.FormClosing += new FormClosingEventHandler(SettingsPage_FormClosing);
            this.SettingsButton.Click += new EventHandler(SettingsButton_Click);
        }
        #endregion

        #region Setting Page methods
        /// <summary>
        /// handles the event of an attempt to close the program
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void SettingsPage_FormClosing(object sender, FormClosingEventArgs e)
        {
            e.Cancel = true;
            this.SP.Hide();
            this.Enabled = true;
            this.Focus();
        }

        /// <summary>
        /// handles the "ok" button click event of the settings form
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void okButton_Click(object sender, EventArgs e)
        {
            this.applyColors(sender, e);
            this.SP.Hide();
            this.Enabled = true;
            this.Focus();
        }

        /// <summary>
        /// handles the "apply" button click event of the settings form
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void SettingsButton_Click(object sender, EventArgs e)
        {
            this.Enabled = false;
            SP.Show();
        }

        /// <summary>
        /// attached the colors selected in the settings page to the color data members
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void applyColors(object sender, EventArgs e)
        {
            Color p1 = Color.FromArgb(this.SP.p1alpha, this.SP.p1red, this.SP.p1green, this.SP.p1blue);
            Color p2 = Color.FromArgb(this.SP.p2alpha, this.SP.p2red, this.SP.p2green, this.SP.p2blue);
            Color board = Color.FromArgb(255, this.SP.boardred, this.SP.boardgreen, this.SP.boardblue);
            Color back = Color.FromArgb(255, this.SP.backred, this.SP.backgreen, this.SP.backblue);
            this.playerOneColor = p1;
            this.playerTwoColor = p2;
            this.boardColor = board;
            this.BackColor = back;
            this.repaintBoard();
        }

        /// <summary>
        /// repaints the entire form according to the color data members
        /// </summary>
        public void repaintBoard()
        {
            for (int i = 0; i < rows; i++)
            {
                for (int j = 0; j < columns; j++)
                {
                    Position pos = new Position(i,j);
                    players player = this.board.GetPlayer(pos);

                    if (player == players.NONE)
                    {
                        this.boardButtons[i, j].BackgroundColor = (this.boardColor);
                    }
                    else if (player == players.PLAYER1)
                    {
                        this.boardButtons[i, j].BackgroundColor = (this.playerOneColor);
                    }
                    else if (player == players.PLAYER2)
                    {
                        this.boardButtons[i, j].BackgroundColor = (this.playerTwoColor);
                    }
                }
            }
            this.Refresh();
        }

        #endregion

        #region MainScreen events

        /// <summary>
        /// handles the "new game" button click event
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void button1_Click(object sender, EventArgs e)
        {
            DialogResult result = MessageBox.Show("Do you want to play against the computer? (Click Yes to play against the computer or No to play against a friend", "Start a new game", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
            if (result == DialogResult.Yes) this.playingAgainstComputer = true;
            else this.playingAgainstComputer = false;

            NewGame();
        }

        private void PlayerRandomize()
        {
            // Decide randomly which player start first
            int starter = (GeneralConsts.random.Next(int.MaxValue) % 2);

            this.currentPlayer = (starter == 1) ? players.PLAYER1 : players.PLAYER2;

            string message = string.Empty;

            switch (this.currentPlayer)
            {
                case players.PLAYER1:
                    message = "Player 1 (Black) is first.";
                    break;
                case players.PLAYER2:
                    message = "Player 2 (White) is first.";
                    break;
                default:
                    break;
            }

            MessageBox.Show(message);
        }

        /// <summary>
        /// handles the "quit" button click event
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void button2_Click(object sender, EventArgs e)
        {
            DialogResult result = MessageBox.Show("Are you sure you want to leave OTHELLO?",  "OTHELLO", MessageBoxButtons.YesNo, MessageBoxIcon.Asterisk);
            if (result==DialogResult.Yes) Environment.Exit(1);
        }

        /// <summary>
        /// handles the "mouse off a board button" event
        /// </summary>
        /// <param name="source"></param>
        /// <param name="e"></param>
        private void button_MouseLeave(object sender, EventArgs e)
        {
            Button button = sender as Button;
            Position position = button.Position;
            players player = board.GetPlayer(position);

            switch (player)
            {
                case players.NONE:
                    button.BackgroundColor = (this.boardColor);
                    break;
                case players.PLAYER1:
                    button.BackgroundColor = (this.playerOneColor);
                    break;
                case players.PLAYER2:
                    button.BackgroundColor = (this.playerTwoColor);
                    break;
                default:
                    break;
            }

            button.Refresh();
        }

        /// <summary>
        /// handles the "mouse on a board button" event
        /// </summary>
        /// <param name="source"></param>
        /// <param name="e"></param>
        private void button_MouseEnter(object sender, EventArgs e)
        {
            Button button = sender as Button;
            Position position = button.Position;

            if (board.IsLegalMove(position))
            {
                if (currentPlayer == players.PLAYER1)
                {
                    button.BackgroundColor = (playerOneColor);
                }
                else
                {
                    button.BackgroundColor = (playerTwoColor);
                }

                button.Refresh();
            }
        }

        /// <summary>
        /// handles a push on one of the board buttons (explained in the attached document)
        /// </summary>
        /// <param name="source"></param>
        /// <param name="e"></param>
        private void button_Click(object sender, EventArgs e)
        {
            // Meaning the user can palce black pieces freely across the board.
            // This is used to help create special cases and to help debugging
            if (this.checkBox1.Checked)
            {
                Button button = sender as Button;

                button.BackgroundColor = playerOneColor;
                this.board.Play(new ActionList(button.Position, new Board(8, 8)) { });
                button.PushDown();
            }
            else
            {
                Button button = sender as Button;
                Position pos = new Position(button.Position);
                TryToPush(pos);
            }
        }

        #endregion

        /// <summary>
        /// does some component initializing beyond the automatic initializing created 
        /// by Visual Studio (such as sets the position of all of the controls...
        /// </summary>
        private void MoreInitializing()
        {
            this.computerTimer.Enabled = false;

            this.BackColor = Color.Cyan;

            for (int i = 0; i < GeneralConsts.LEGAL_BOARD_SIZE ; i++)
            {
                for (int j = 0; j < GeneralConsts.LEGAL_BOARD_SIZE; j++)
                {
                    Button button = this.boardButtons[i, j] = new Button(this.boardColor, i, j);
                    this.tableLayoutPanel1.Controls.Add(button, i, j);

                    button.Click += new EventHandler(button_Click);
                    button.MouseEnter += new EventHandler(button_MouseEnter);
                    button.MouseLeave += new EventHandler(button_MouseLeave);
                }
            }

            this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
            this.Name = "Othello";
            this.Text = "Othello";
            this.ResumeLayout(false);

            this.PerformLayout();

            WR.readWeights();

            NewGame();
        }

         WeightsReader WR = new WeightsReader();

        /// <summary>
        /// resets the game board for a new game
        /// </summary>
        private void ResetButtonsLook()
        {
            int rowHalf = rows / 2;
            int columnsHalf = columns / 2;

            for (int i = 0; i < rows; i++)
            {
                for (int j = 0; j < columns; j++)
                {
                    this.boardButtons[i, j].BackgroundColor = (this.boardColor);
                    this.boardButtons[i, j].Refresh();
                }
            }

            boardButtons[rowHalf, columnsHalf].BackgroundColor = (playerTwoColor);
            boardButtons[rowHalf, columnsHalf].Refresh();
            boardButtons[rowHalf - 1, columnsHalf - 1].BackgroundColor = (playerTwoColor);
            boardButtons[rowHalf - 1, columnsHalf - 1].Refresh();
            boardButtons[rowHalf - 1, columnsHalf].BackgroundColor = (playerOneColor);
            boardButtons[rowHalf - 1, columnsHalf].Refresh();
            boardButtons[rowHalf, columnsHalf - 1].BackgroundColor = (playerOneColor);
            boardButtons[rowHalf, columnsHalf - 1].Refresh();
        }

        /// <summary>
        /// make an attempt to push a board button by the current player. 
        /// if it's possible and the push has an effect on the board, 
        /// the changes are made accordingly and hands the turn to the next player
        /// </summary>
        /// <param name="pos"></param>
        public void TryToPush(Position pos)
        {
            // Get the ActionList with the same position
            ActionList AL = board.WhatCanIDo.FirstOrDefault(item => item.Pos.Equals(pos));

            // The position was found among the moves the player can take
            if (AL != null)
            {
                Button button = boardButtons[pos.Row, pos.Column];
                if (currentPlayer == players.PLAYER1)
                {
                    button.BackgroundColor = (playerOneColor);
                }
                else
                {
                    button.BackgroundColor = (playerTwoColor);
                }

                button.PushDown();
                this.board.Play(AL);
                this.MakeBoardChanges(currentPlayer, pos, AL);
                this.NextPlayer();
            }
        }

        /// <summary>
        /// used to make changes on the board following a move was made by a player (handles a series of action --> ActionList AL)
        /// </summary>
        /// <param name="player"></param>
        /// <param name="pos"></param>
        /// <param name="AL"></param>
        private void MakeBoardChanges(players player, Position pos, ActionList AL)
        {
            for (int i = 0; i < AL.Current; i++)
            {
                if (player == players.PLAYER1)
                {
                    performAction(pos, AL.Actions[i], players.PLAYER1);
                }
                else performAction(pos, AL.Actions[i], players.PLAYER2);
            }
        }

        //used to make changes on the board following a move made by a player (handles a single action --> Action action), used by the "makeBoardChanges" function
        private void performAction(Position pos, Action action, players player)
        {
            int i = pos.Row + action.Direction.Vertical;
            int j = pos.Column + action.Direction.Horizontal;
            int counter = 0;
            while (counter < action.Ammount)
            {
                Button button = this.boardButtons[i, j];
                if (currentPlayer == players.PLAYER1) button.BackgroundColor = (playerOneColor);
                else button.BackgroundColor = (playerTwoColor);
                button.PushDown();
                i += action.Direction.Vertical;
                j += action.Direction.Horizontal;
                counter++;
            }
        }

        /// <summary>
        /// Hand down the turn to the next player and checks if the current player can play. 
        /// If current player has no possible moves the turn is handed back to the first player.
        /// if none can play the function checks who the winer is, announces it using a message 
        /// box and updates the score (it doesn't reset the game)
        /// </summary>
        private void NextPlayer()
        {
            int tries = 0;
            Score score = board.GetBoardScore();
            players leadingPlayer = score.WhoIsLeading();
            this.UpdateLeadingText(leadingPlayer);

            // If the game is over
            if (score.IsGameOver())
            {
                tries = 2;
            }

            bool canPlay = false;
            while (!canPlay)
            {
                if (tries < 2)
                {
                    // Switch players and check if he can move on the board
                    currentPlayer = SwitchPlayer();
                    canPlay = board.CanPlayerPlay();

                    // If it's the computer turn
                    if ((canPlay) && (currentPlayer == players.PLAYER2) && playingAgainstComputer)
                    {
                        this.PlayWithComputer();
                    }
                }
                // If the game is over or neither of the players can play,
                if (tries == 2)
                {
                    String winnerText = null;
                    if (leadingPlayer == players.NONE)
                    {
                        this.ties += 1;
                        winnerText = null;
                    }
                    if (leadingPlayer == players.PLAYER1)
                    {
                        this.playerOneWins += 1;
                        winnerText = "Player 1";
                    }
                    if (leadingPlayer == players.PLAYER2)
                    {
                        this.playerTwoWins += 1;
                        winnerText = "player 2";
                    }

                    this.UpdateScore();
                    currentPlayer = players.PLAYER1;

                    // Create the message to show
                    if (winnerText == null)
                    {
                        winnerText = "Game tied";
                    }
                    else
                    {
                        winnerText = "Congratulations!!! \n " + winnerText + " won!!!";
                    }

                    // Show the message and announce the winner
                    MessageBox.Show(winnerText, "Othello", MessageBoxButtons.OK, MessageBoxIcon.None);
                    return;
                }
                tries++;
            }
        }

        /// <summary>
        /// Switch between the players
        /// </summary>
        /// <returns>The next player</returns>
        public players SwitchPlayer()
        {
            this.board.SwitchPlayers();
            currentPlayer = this.board.CurrentPlayer;
            return currentPlayer;
        }

        /// <summary>
        /// if (playingAgainsComputer==true) and (curretPlayer==Players.Player2) this function 
        /// is called the function decides the computers next move and performs it
        /// </summary>
        private void PlayWithComputer()
        {
            // Disable the game board user interface
            this.Enabled = false;
            this.computerTimer.Interval = 200;
            this.computerTimer.Tick += new EventHandler(ComputerTimer_Tick);
            this.computerTimer.Enabled = true;
        }

        /// <summary>
        /// Handles the animation of the computer player 
        /// (delay it's move, make it looks like it "thinks" before it acts)
        /// </summary>
        /// <param name="source"></param>
        /// <param name="e"></param>
        private void ComputerTimer_Tick(object source, EventArgs e)
        {
            // Change the mouse cursor to waiting sign
            this.Cursor = Cursors.WaitCursor;

            // Diable the timer and unsubscribe the tick event
            this.computerTimer.Enabled = false;
            this.computerTimer.Tick -= new EventHandler(ComputerTimer_Tick);

            // Choose best course of action
            ActionList AL = FindBest(this.board);

            if (AL != null)
            {
                TryToPush(AL.Pos);
            }

            // Change back the nouse cursor. Enable the game board user interface
            this.Cursor = Cursors.Arrow;
            this.Enabled = true;
        }

        /// <summary>
        /// Find the best ActionList to play from a List of ActionLists
        /// </summary>
        /// <param name="board">The root of the Minimax tree</param>
        /// <returns>The best ActionList to play</returns>
        public ActionList FindBest(Othello_Game.Board board)
        {
            ActionList al = GeneralConsts.Instance.LearningAlgorithm.FindBest(board, Difficulty, this);
            return al;
        }

        /// <summary>
        /// updates the player on who's currently leading (using a text box on the main game form)
        /// </summary> 
        /// <param name="player">the leading player's name</param>
        private void UpdateLeadingText(players player)
        {
            string leader;
            if (player == players.NONE) leader = "Tie";
            else if (player == players.PLAYER1) leader = "Black";
            else leader = "White";
            this.leadingTextBox.Text = leader;
        }

        /// <summary>
        /// updates the player on the current score (using tex boxes on the main game form)
        /// </summary>
        private void UpdateScore()
        {
            this.blackWinsTextBox.Text = this.playerOneWins.ToString();
            this.whiteWinsTextBox.Text = this.playerTwoWins.ToString();
            this.tieAmmountTextBox.Text = this.ties.ToString();
        }

        /// <summary>
        /// resets the board and the current player for a new game
        /// </summary>
        public void NewGame()
        {
            GeneralConsts.Instance.AttributeWeights = WR.Weights[(GeneralConsts.random.Next(int.MaxValue ) % 3)];
          
            this.ResetButtonsLook();

            PlayerRandomize();

            this.board.ResetBoard(this.currentPlayer);

            if ((currentPlayer == players.PLAYER2) && playingAgainstComputer)
            {
                this.PlayWithComputer();
            }
        }

        public void SetProgressBarLimit(int limit)
        {
            this.toolStripProgressBar1.Maximum = limit;
        }

        public void SetProgressBar(int howMuch)
        {
            this.toolStripProgressBar1.Value = howMuch;
        }
    }
}
